在前一篇文章中,我們建立了一個空白的視窗,並且加入了一個紅色的正方體。這一篇文章我們要來畫出相機模型,讓我們可以看到相機的位置和方向。
一般來說,我們可以用各種3D物體放在空間中,代表相機的位置和朝向,也可以簡單的畫一個箭頭、或是一個 3D 座標軸來代表相機。不過實務上,我們常用的是視錐體(frustum)來代表相機的位置和方向,因為這樣可以直觀的看到相機的視野範圍和朝向。
視錐其實就是一個四面體,有一個頂點代表相機的位置,另外四個頂點代表相機的視野範圍,我們可以用一條條線段,畫出這個四面體的邊。假設我們的中心在 (0, 0, 0) 的位置,然後朝向 z=+1 的方向,每個頂點分別在 (±0.5, ±0.5, 1) 的位置,把線畫出來,以下是一個簡單的範例:
import sys
from vispy import app, scene
# Create canvas
canvas = scene.SceneCanvas(title="vispy tutorial", keys="interactive", show=True)
# Make color white
canvas.bgcolor = "white"
# Create view and set the viewing camera
view = canvas.central_widget.add_view()
view.camera = "turntable"
view.camera.fov = 50
view.camera.distance = 10
def create_frustum():
center = np.array([0, 0, 0])
points = np.array([
[0.5, 0.5, 1],
[0.5, -0.5, 1],
[-0.5, -0.5, 1],
[-0.5, 0.5, 1],
])
for i in range(4):
line = scene.visuals.Line(pos=np.array([center, points[i]]), color="red", antialias=True, width=2, parent=view.scene)
line = scene.visuals.Line(pos=np.array([points[i], points[(i + 1) % 4]]), color="red", antialias=True, width=2, parent=view.scene)
create_frustum()
if __name__ == "__main__":
if sys.flags.interactive != 1:
app.run()
就會得到以下的畫面:
而大部分的圖片都是不是方形的,因此我們加上 aspect_ratio 這個參數調整,再加上一個半透明的平面代表相機的成像平面:
def create_frustum(aspect_ratio=1.3):
center = np.array([0, 0, 0])
points = np.array([
[0.5, 0.5, 1],
[0.5, -0.5, 1],
[-0.5, -0.5, 1],
[-0.5, 0.5, 1],
])
points[:, 0] *= aspect_ratio
for i in range(4):
line = scene.visuals.Line(pos=np.array([center, points[i]]), color="red", antialias=True, width=2, parent=view.scene)
line = scene.visuals.Line(pos=np.array([points[i], points[(i + 1) % 4]]), color="red", antialias=True, width=2, parent=view.scene)
# Create the semi-transparent plane
plane = scene.visuals.Polygon(pos=points, color=(1, 0, 0, 0.5), parent=view.scene)
# Here the z-axis of the plane is ignored, so we need to translate it
plane.transform = scene.transforms.MatrixTransform()
plane.transform.translate([0, 0, 1])
create_frustum()
如果我們有圖片的檔案,我們也可以把圖片投影到這個平面上,這樣就可以看到相機的成像效果。在vispy中,我們可以透過 scene.visuals.Image
來加入圖片:
def create_frustum_with_image(image_path):
# Read image and convert to numpy array. Make values in [0, 1]
image = Image.open(image_path)
width, height = image.size
aspect_ratio = width / height
image = np.array(image).astype(np.float32) / 255.0
# Add alpha channel
alpha = np.ones((height, width, 1), dtype=np.float32) * 0.5
image = np.concatenate([image, alpha], axis=2)
# Create the image visual
image_visual = scene.visuals.Image(image, parent=view.scene)
# Scale and move the image to fit the frustum plane
image_scaling = 1.0 / height
image_translate = (-width / 2.0 * image_scaling, -height / 2.0 * image_scaling)
image_visual.transform = scene.transforms.STTransform(scale=(image_scaling, image_scaling), translate=image_translate)
z_transform = scene.transforms.MatrixTransform()
z_transform.translate([0, 0, 1.0])
image_visual.transform = z_transform * image_visual.transform
center = np.array([0, 0, 0])
points = np.array([
[0.5, 0.5, 1],
[0.5, -0.5, 1],
[-0.5, -0.5, 1],
[-0.5, 0.5, 1],
])
points[:, 0] *= aspect_ratio
for i in range(4):
line = scene.visuals.Line(pos=np.array([center, points[i]]), color="red", antialias=True, width=2, parent=view.scene)
line = scene.visuals.Line(pos=np.array([points[i], points[(i + 1) % 4]]), color="red", antialias=True, width=2, parent=view.scene)
create_frustum_with_image("lena.png")
主要的步驟是讀取圖片,然後把圖片加入到 scene.visuals.Image
中,最後透過 scene.transforms.STTransform
來縮放和移動圖片(這部分有點繁複,但主要是把圖片塞到視錐的平面上)。最後的結果如下: